//============================================================================
// Name        : User.cpp
// Author      : zhou zhao
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#include <iostream>

#define SERVERPORT "3490" //server port client will connect to
#define MAXDATASIZE 100
#define MAXLINE 100

using namespace std;

//translate general socket address to IPv4 or IPv6 address
void* sockAddrTranslate(struct sockaddr* sockAddr){
	if(sockAddr->sa_family == AF_INET){
		return &(((struct sockaddr_in*)sockAddr)->sin_addr);
	}else{
		return &(((struct sockaddr_in6*)sockAddr)->sin6_addr);
	}
}

int main(int argc, char* argv[]) {

	FILE* fileHandle;
	char buffer[MAXLINE];
	char username[MAXLINE];
	char password[MAXLINE];
	char portnumber[MAXLINE];
	string user, pass, portnum, login;

	int sockFD;
	struct addrinfo hints, *serveInfo, *servePtr;
	int returnValue;

	char ipAddr[INET6_ADDRSTRLEN];
	char data[MAXDATASIZE];
	int numByteRecv;
	int numByteSend;

	string superIP, superPort;
	char supernodeIP[MAXLINE];
	char supernodePort[MAXLINE];

	struct sockaddr_storage superAddr;
	socklen_t superAddrSize;

	//check the hostname of server is import
	if(argc != 4){
		perror("usage: ./Users serverIP UserPass.txt UserText.txt");
		exit(1);
	}

	//open user pass file
	if((fileHandle = fopen(argv[2], "r")) == NULL){
		perror("client: fopen has error");
		exit(1);
	}

	//import user pass information
	while(fgets(buffer, MAXLINE, fileHandle) != NULL){
		if(sscanf(buffer, "%s %s %s", username, password, portnumber) == 3){
			user = string(username);
			pass = string(password);
			portnum = string(portnumber);
			cout<<"username:"<<user<<" password:"<<pass<<" portnumber:"<<portnum<<endl;
		}
	}
	fclose(fileHandle); //close user pass file

	//prepare address info
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;

	if((returnValue = getaddrinfo(argv[1], SERVERPORT, &hints, &serveInfo)) != 0){
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(returnValue));
		exit(1);
	}

	for(servePtr = serveInfo; servePtr != NULL; servePtr = servePtr->ai_next){
		//construct a socket
		if((sockFD = socket(servePtr->ai_family, servePtr->ai_socktype,
				servePtr->ai_protocol)) == -1){
			perror("client: socket has error");
			continue;
		}
		//connect socket
		if(connect(sockFD, servePtr->ai_addr, servePtr->ai_addrlen) == -1){
			close(sockFD);
			perror("client: connect has error");
			continue;
		}
		break;
	}
	if(servePtr == NULL){
		perror("client: servePtr is NULL");
		exit(1);
	}

	//print connected server info
	inet_ntop(servePtr->ai_family, sockAddrTranslate((struct sockaddr*)servePtr->ai_addr),
			ipAddr, sizeof(ipAddr));
	cout<<"client: connecting to server "<<ipAddr<<endl;

	freeaddrinfo(serveInfo);

	login = user.insert(0, "Login#")+" "+pass+" "+portnum;
	if((numByteSend = send(sockFD, login.c_str(), login.size(), 0)) == -1){
		perror("client: send has error");
		exit(1);
	}

	if((numByteRecv = recv(sockFD, data, MAXDATASIZE-1, 0)) == -1){
			perror("client: recv has error");
			exit(1);
	}

	data[numByteRecv] = '\0';
	string temp = string(data);
	cout<<temp<<endl;

	if(temp.compare(0, 9, string("Accepted#")) == 0){
		if(sscanf(data, "%s %s", supernodeIP, supernodePort) == 2){
			superIP = string(supernodeIP).erase(0, 9);
			superPort = string(supernodePort);
			cout<<"supernodeIP:"<<superIP<<" supernodePort:"<<superPort<<endl;
		}
	}
	close(sockFD);
	cout<<"client will sleep ..."<<endl;
	sleep(5);
	cout<<"client is wake up"<<endl;

	//open user text file
	if((fileHandle = fopen(argv[3], "r")) == NULL){
		perror("client: fopen has error");
		exit(1);
	}

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM;

	if((returnValue = getaddrinfo(superIP.c_str(), superPort.c_str(),
			&hints, &serveInfo)) != 0){
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(returnValue));
		exit(1);
	}

	for(servePtr = serveInfo; servePtr != NULL; servePtr = servePtr->ai_next){
		if((sockFD = socket(servePtr->ai_family, servePtr->ai_socktype,
				servePtr->ai_protocol)) == -1){
			perror("client: socket has error");
			continue;
		}
		break;
	}
	if(servePtr == NULL){
		perror("client: fail to bind UDP socket");
		exit(1);
	}

	while(fgets(buffer, MAXLINE, fileHandle) != NULL){
		cout<<buffer<<endl;
		if((numByteSend = sendto(sockFD, buffer, strlen(buffer), 0,
				servePtr->ai_addr, servePtr->ai_addrlen)) == -1){
			perror("client: sendto has error");
			exit(1);
		}
	}
	fclose(fileHandle); //close user text file
	freeaddrinfo(serveInfo);
	close(sockFD);

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM;
	hints.ai_flags = AI_PASSIVE;

	if((returnValue = getaddrinfo(NULL, portnumber, &hints, &serveInfo)) != 0){
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(returnValue));
		exit(1);
	}

	for(servePtr = serveInfo; servePtr != NULL; servePtr = servePtr->ai_next){
		if((sockFD = socket(servePtr->ai_family, servePtr->ai_socktype, servePtr->ai_protocol)) == -1){
			perror("client: socket has error");
			continue;
		}
		if(bind(sockFD, servePtr->ai_addr, servePtr->ai_addrlen) == -1){
			close(sockFD);
			perror("client: bind has error");
			continue;
		}
		break;
	}

	if(servePtr == NULL){
		perror("client: servePtr is NULL");
		exit(1);
	}
	freeaddrinfo(serveInfo);

	superAddrSize = sizeof(superAddr);
	cout<<"client is waiting for data from supernode"<<endl;
	while(1){
		if((numByteRecv = recvfrom(sockFD, data, MAXDATASIZE-1, 0,
				(struct sockaddr*)&superAddr, &superAddrSize)) == -1){
			perror("client: recvfrom has error");
			exit(1);
		}
		data[numByteRecv] = '\0';
		cout<<data<<endl;
	}
	return 0;
}
